﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework;
using System.Runtime.InteropServices;

namespace Tomahawk.Runtime.Rendering.Materials
{
    #region ScreenQuad
    internal class ScreenQuad : IDisposable
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct VertexQuad
        {
            public Vector3 Position;
            public Vector3 UVW;

            public static int SizeInBytes { get { return 6 * sizeof(float); } }
            public static readonly VertexElement[] VertexElements = new VertexElement[]
            {
               new VertexElement(0, 0, VertexElementFormat.Vector3,
                VertexElementMethod.Default, VertexElementUsage.Position, 0),
               new VertexElement(0, sizeof(float) * 3, VertexElementFormat.Vector3,
                VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0),
            };

            public VertexQuad(Vector3 pos, Vector3 uvw)
            {
                Position = pos;
                UVW = uvw;
            }
        }

        VertexDeclaration vertexDecl = null;
        VertexQuad[] verts = null;
        short[] ib = null;

        public ScreenQuad(GraphicsDevice gd)
        {
            vertexDecl = new VertexDeclaration(gd, VertexQuad.VertexElements);
            verts = new VertexQuad[]
                {
                    new VertexQuad(new Vector3(0,0,0), new Vector3(1,1,0)),
                    new VertexQuad(new Vector3(0,0,0), new Vector3(0,1,0)),
                    new VertexQuad(new Vector3(0,0,0), new Vector3(0,0,0)),
                    new VertexQuad(new Vector3(0,0,0), new Vector3(1,0,0))
                };

            ib = new short[] { 0, 1, 2, 2, 3, 0 };
        }

        public void Render(Vector2 v1, Vector2 v2, GraphicsDevice gd)
        {
            gd.VertexDeclaration = vertexDecl;

            verts[0].Position.X = v2.X;
            verts[0].Position.Y = v1.Y;

            verts[1].Position.X = v1.X;
            verts[1].Position.Y = v1.Y;

            verts[2].Position.X = v1.X;
            verts[2].Position.Y = v2.Y;

            verts[3].Position.X = v2.X;
            verts[3].Position.Y = v2.Y;

            gd.DrawUserIndexedPrimitives<VertexQuad>
                (PrimitiveType.TriangleList, verts, 0, 4, ib, 0, 2);
        }


        #region IDisposable Members
        public void Dispose()
        {
            if (vertexDecl != null)
            {
                vertexDecl.Dispose();
            }
        }
        #endregion
    }
    #endregion

    public class SASScene : SASMaterial
    {
        #region Events
        public delegate bool DrawGeometryDelegate(GraphicsDevice device);
        public event DrawGeometryDelegate OnDrawGeometry = null;
        public bool RaiseDrawGeometry(GraphicsDevice device) { return OnDrawGeometry(device); }
        #endregion

        internal string currentDepthStencilBuffer = "";
        internal Dictionary<int,string> currentRenderTarget = new Dictionary<int, string>();
        internal ScreenQuad screenQuad = null;

        public SASScene()
        {
        }

        public SASScene(string effectFile, ContentManager content) :
            base(effectFile, content)
        {
        }
        
        public override bool Initialize(string effectFile, ContentManager content)
        {
            bool result = base.Initialize(effectFile, content);

            if (result)
            {
                SASScriptParameter script = Parameters.Get<SASScriptParameter>("SCRIPT");
                result = (script == null) || (script.ScriptClass != SASScriptParameter.ScriptClassType.Scene);
            }

            return result;
        }

        #region Surfaces
        internal bool LoadSurfaces(GraphicsDevice gd)
        {
            bool result = true;
            foreach (KeyValuePair<string,SASParameter> parameter in parameters)
            {
                if (parameter.Value is SASRenderTargetParameter)
                {
                    result &= (parameter.Value as SASRenderTargetParameter).LoadRenderTarget(gd);
                }
                if (parameter.Value is SASDepthStencilParameter)
                {
                    result &= (parameter.Value as SASDepthStencilParameter).LoadDepthStencilBuffer(gd);
                }
            }

            screenQuad = new ScreenQuad(gd);

            return result;
        }

        internal bool UnloadSurfaces(GraphicsDevice gd)
        {
            bool result = true;
            foreach (SASParameter parameter in parameters)
            {
                if (parameter is SASRenderTargetParameter)
                {
                    result &= (parameter as SASRenderTargetParameter).UnloadRenderTarget();
                }
                if (parameter is SASDepthStencilParameter)
                {
                    result &= (parameter as SASDepthStencilParameter).UnloadDepthStencilBuffer();
                }
            }

            if (screenQuad != null)
            {
                screenQuad.Dispose();
                screenQuad = null;
            }

            return result;
        }

        internal bool ActivateDepthStencilBuffer(string name, GraphicsDevice gd)
        {
            bool result = true;

            SASParameter sasParameter = Parameters[currentDepthStencilBuffer];
            if (sasParameter != null && (sasParameter is SASDepthStencilParameter))
            {
                SASDepthStencilParameter bufferParameter = (SASDepthStencilParameter)sasParameter;
                gd.DepthStencilBuffer = bufferParameter.PreviousBuffer;
            }

            sasParameter = Parameters[name];
            if (sasParameter != null && (sasParameter is SASDepthStencilParameter))
            {
                SASDepthStencilParameter bufferParameter = (SASDepthStencilParameter) sasParameter;
                bufferParameter.PreviousBuffer = gd.DepthStencilBuffer;
                gd.DepthStencilBuffer = bufferParameter.CurrentBuffer;
            }

            currentDepthStencilBuffer = name;

            return result;
        }

        internal bool ActivateRenderTarget(int index, string name, GraphicsDevice gd)
        {
            bool result = true;

            SASParameter sasParameter = Parameters[name];
            if (sasParameter != null && (sasParameter is SASRenderTargetParameter))
            {
                gd.SetRenderTarget(index, ((SASRenderTargetParameter)sasParameter).RenderTarget);
            }
            else
            {
                gd.SetRenderTarget(index, null);
            }

            string rendertarget = null;
            if (currentRenderTarget.TryGetValue(index, out rendertarget))
            {
                if (sasParameter == null)
                {
                    currentRenderTarget.Remove(index);
                }

                sasParameter = Parameters[rendertarget];
                if (sasParameter != null && (sasParameter is SASRenderTargetParameter))
                {
                    SASRenderTargetParameter renderTargetParameter = (SASRenderTargetParameter)sasParameter;
                    renderTargetParameter.TextureValue = renderTargetParameter.RenderTarget.GetTexture();
                }
            }

            currentRenderTarget[index] = name;

            return result;
        }
        #endregion

        #region Rendering
        public override bool DrawQuad(GraphicsDevice gd)
        {
            if (screenQuad != null)
            {
                screenQuad.Render(Vector2.One * -1.0f, Vector2.One * 1.0f, gd);
                return true;
            }
            return false;
        }

        public override bool DrawIndexedPrimitives(PrimitiveType primitiveType, int baseVertex, int minVertexIndex, int numVertices, int startIndex, int primitiveCount, GraphicsDevice device)
        {
            return false;
        }

        public override bool DrawMesh(ModelMesh mesh, ModelMeshPart part, GraphicsDevice device)
        {
            return false;
        }

        public override bool DrawScene(GraphicsDevice device)
        {
            bool result = true;
            SASTechnique currentTechnique = null;
            if (techniqueTable.TryGetValue(effect.CurrentTechnique.Name.ToUpper(), out currentTechnique))
            {
                effect.Begin();
                result = currentTechnique.DrawScene(device);
                effect.End();
            }

            return result;
        }

        public override bool DrawExternal(string script, GraphicsDevice device)
        {
            switch (script)
            {
                case "COLOR": SASMaterial.currentRenderMode = RenderModes.Color; break;
                case "DEPTH": SASMaterial.currentRenderMode = RenderModes.Depth; break;
            }

            return RaiseDrawGeometry(device);
        }

        #endregion
    }
}
